/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019.
 * Description: function of fiq_glue_register
 * Author: xiaojiangfeng <xiaojiangfeng@huawei.com>
 * Create: 2018-09-15
 */

#include <linux/init.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/proc_fs.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <asm/fiq.h>
#include <asm/delay.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <asm/errno.h>
#include <asm/cacheflush.h>
#include <asm/outercache.h>
#ifdef CONFIG_RTOS_EARLY_KBOX
#include <linux/early_kbox.h>
#endif
#ifdef CONFIG_HISI_WATCHDOG
#include <linux/hal/watchdog.h>
#endif
#include <linux/hal/gpio_lock.h>
#include "../../fiq_glue_com/fiq_glue_private.h"
#include "../../fiq_glue_com/fiq_routeway_register.h"

struct fiq_private_data fiq_priv;
static DEFINE_PER_CPU(unsigned int, per_fiq_num);
static DEFINE_PER_CPU(struct pt_regs, per_fiq_args);
static bool ipi_flag;
static unsigned int online_cpus;

/* flag of stop record watchdog timestamp */
bool entry_fiq_mode;

void fiq_clean_source(void)
{
	if (fiq_priv.callback.fiq_cleansourse_handle)
		(*fiq_priv.callback.fiq_cleansourse_handle)();

#ifdef CONFIG_HISI_WATCHDOG
	if (fiq_priv.fiq_clean_wtg)
		call_hwdog_handle_nolock();
#endif
}

void fiq_get_num(void)
{
	unsigned int fiq_num;
	int cpu;

	if (fiq_priv.fiq_source != FIQ_GIC_SOURCE)
		return;
	if (fiq_priv.gic_handle.get_fiq_num) {
		fiq_num = (*fiq_priv.gic_handle.get_fiq_num)();
		if (fiq_priv.gic_handle.eoi_fiq)
			(*fiq_priv.gic_handle.eoi_fiq)(fiq_num);
	} else {
		fiq_num = 0;
	}

	if (fiq_priv.fiq_type == FIQ_1_N_TYPE) {
		if (fiq_num != 0) {
			for_each_possible_cpu(cpu) {
				per_cpu(per_fiq_num, cpu) = fiq_num;
			}
			ipi_flag = true;
		}
	} else {
		for_each_possible_cpu(cpu) {
			per_cpu(per_fiq_num, cpu) = fiq_num;
		}
	}
}

struct fiq_num_node *fiq_find_node(unsigned int fiq_num)
{
	struct fiq_num_node *fiq_node = NULL;
	struct list_head *t = NULL;

	list_for_each(t, &fiq_priv.entry) {
		fiq_node = list_entry(t, struct fiq_num_node, node);
		if (fiq_node->fiq_num == fiq_num)
			return fiq_node;
	}

	return NULL;
}

struct fiq_callback_node *fiq_find_callback(struct fiq_num_node *fiq_node,
	void (*fiq_callback_func)(unsigned int))
{
	struct fiq_callback_node *callback_node = NULL;
	struct list_head *t = NULL;

	list_for_each(t, &fiq_node->callback_head) {
		callback_node = list_entry(t, struct fiq_callback_node, node);
		if (callback_node->callback == fiq_callback_func)
			return callback_node;
	}

	return NULL;
}

void fiq_free_callback(struct fiq_num_node *fiq_node)
{
	struct fiq_callback_node *callback_node = NULL;
	struct list_head *t = NULL;
	struct list_head *n = NULL;

	list_for_each_safe(t, n, &fiq_node->callback_head) {
		callback_node = list_entry(t, struct fiq_callback_node, node);
		list_del(&callback_node->node);
		kfree(callback_node);
	}
}


static void fiq_save_regs(struct pt_regs *args, void *info)
{
	struct fiq_func_params *params = (struct fiq_func_params *)info;
	struct pt_regs *regs = params->regs;

	memcpy(args, regs, sizeof(struct pt_regs));
}

struct pt_regs *fiq_get_regs(void)
{
	int cpu;

	cpu = raw_smp_processor_id();
	return &per_cpu(per_fiq_args, cpu);
}
EXPORT_SYMBOL(fiq_get_regs);

static void fiq_glue_action_pack(void *info)
{
	struct fiq_num_node *fiq_node = NULL;
	struct fiq_callback_node *callback_node = NULL;
	struct list_head *t = NULL;
	struct pt_regs *args = NULL;
	int cpu;

	unsigned int fiq_num = 0;

	cpu = raw_smp_processor_id();
	if (fiq_priv.fiq_source == FIQ_GIC_SOURCE)
		fiq_num = per_cpu(per_fiq_num, cpu);

	args = &per_cpu(per_fiq_args, cpu);
	fiq_save_regs(args, info);

	fiq_node = fiq_find_node(fiq_num);
	if (!fiq_node) {
		printk("no fiq%u callback\n", fiq_num);
		goto out;
	}

	list_for_each(t, &fiq_node->callback_head) {
		callback_node = list_entry(t, struct fiq_callback_node, node);
		callback_node->callback(fiq_num);
	}

#ifdef CONFIG_RTOS_EARLY_KBOX
	/* in ekbox reserve mem, the first n bytes is 'abcd*', next n bytes is 'ABCD*' */
	if (ekbox_pcontent)
		*(ekbox_pcontent + online_cpus + smp_processor_id()) = 'A' + smp_processor_id();
#endif
	/* flush all cache */
	flush_cache_all();
#if defined(CONFIG_CORTEX_A9) && defined(CONFIG_HISI_L2_OUTER_CACHE)
	/* modify to avoid get wrong kbox id caused by flash cache */
	outer_clean_all();
#else
	outer_flush_all();
#endif
out:
	/* for avoid FIQ reentry, some boards need to dead loop */
	if (fiq_priv.fiq_deadloop) {
		while (1)
			;
	}
}

static void fiq_glue_action(struct fiq_glue_handler *h, void *regs, void *svc_sp)
{
	struct fiq_func_params params;
	void *info = &params;

	params.h = h;
	params.regs = regs;
	params.svc_sp = svc_sp;

	entry_fiq_mode = true;

	/* bust_spinlock here bucause rtos_gpio_spinlock_init() might call printk */
	bust_spinlocks(1);
	/*
	 * reinit rtos_gpio_spinlock here to avoid deadlock in fiq
	 * when call printk while hold this spinlock which will
	 * try to get this spinlock.
	 */
	rtos_gpio_spinlock_init();
#ifdef CONFIG_SMP
	if (FIQ_1_N_TYPE == fiq_priv.fiq_type && ipi_flag) {
		ipi_flag = false;
		if (fiq_priv.gic_handle.raise_softfiq)
			(*fiq_priv.gic_handle.raise_softfiq)();
	}
#endif
	fiq_glue_action_pack(info);
}

static void fiq_glue_default_action(struct fiq_glue_handler *h, void *regs, void *svc_sp)
{
	printk("fiq default action\n");
}

struct fiq_glue_handler fiq_glue_handler = {
	.fiq = fiq_glue_default_action,
};

static int fiq_glue_handler_set(void)
{
	struct fiq_glue_handler *handler = &fiq_glue_handler;

	handler->fiq = fiq_glue_action;
	handler->resume = NULL;
	if (handler->private)
		return -EBUSY;
	handler->private = kzalloc(FIQ_GLUE_PRIVATE_SZ, GFP_KERNEL);
	if (!handler->private)
		return -ENOMEM;
	return 0;
}

static void fiq_glue_handler_reset(void)
{
	fiq_glue_handler.fiq = fiq_glue_default_action;
	fiq_glue_handler.resume = NULL;
	kfree(fiq_glue_handler.private);
	fiq_glue_handler.private = NULL;
}

static void fiq_call_gic_set(void *args)
{
	struct fiq_gic_info *info = (struct fiq_gic_info *)args;

	(*fiq_priv.gic_handle.set_fiq)(info->fiq, info->priority);
}

static bool fiq_glue_source_is_watchdog(void)
{
	if (fiq_priv.fiq_source != FIQ_WDG_SOURCE)
		return false;

	return true;
}

static bool fiq_glue_type_is_n_n(void)
{
	if (fiq_priv.fiq_type == FIQ_N_N_TYPE)
		return true;

	return false;
}

static int fiq_set_gic(struct device_node *node)
{
#ifdef CONFIG_OF_IRQ
	struct fiq_gic_info info;
	u32 i;
	int ret = 0;
	struct of_phandle_args oirq;

	if (!fiq_priv.gic_handle.set_fiq) {
		printk(KERN_ERR "[%s]set_fiq handle is NULL, Can't set any fiq.\n", __func__);
		return -EINVAL;
	}

	if (fiq_glue_source_is_watchdog()) {
		pr_info("%s, source is watchdog, Don't set\n", __func__);
		return 0;
	}

	for (i = 0; i < g_fiq_glue_no_info.fiq_num; i++) {
		ret = of_irq_parse_one(node, i, &oirq);
		if (ret) {
			printk(KERN_ERR "[%s]can't find fiq list%u, ret %d\n",
				__func__, i, ret);
			continue;
		}
		ret = irq_create_of_mapping(&oirq);
		if (!ret) {
			printk(KERN_ERR "[%s] irq create mapping fail", __func__);
			return -EINVAL;
		}

		info.fiq = ret;
		info.priority = 0;
		printk(KERN_INFO "[%s]set fiq%d priority:%d, args0=%d\n", __func__, info.fiq, info.priority, oirq.args[0]);

		ret = (*fiq_priv.gic_handle.set_fiq)(info.fiq, info.priority);
		if (ret) {
			printk(KERN_ERR"[%s]set fiq%d error.\n", __func__, info.fiq);
			continue;
		}

		if (fiq_glue_type_is_n_n())
			smp_call_function(fiq_call_gic_set, &info, false);

		pr_info("[%s]set %u irq to fiq\n", __func__, info.fiq);
	}

	return ret;
#else
	return -ENOSYS;
#endif
}

static int fiq_priv_init(void)
{
	int ret;
	struct device_node *node;

	node = of_find_compatible_node(NULL, NULL, FIQ_DTS_COMPATIBLE_STRING);
	if (WARN_ON(!node)) {
		printk(KERN_ERR "[%s]find dts node error!\n", __func__);
		return ERR_DTS_PARSE;
	}

	if (fiq_priv.callback.fiq_init_handle) {
		ret = (*fiq_priv.callback.fiq_init_handle)();
		if (ret) {
			printk(KERN_ERR"init handle error%d\n", ret);
			return ret;
		}
	}

	ret = fiq_set_gic(node);
	if (ret) {
		printk(KERN_ERR "[%s]fiq set gic error!\n", __func__);
		return ERR_DTS_PARSE;
	}

	return 0;
}

static int fiq_glue_init(void)
{
	int ret;

	ret = fiq_glue_handler_set();
	if (ret) {
		printk(KERN_ERR"fiqglue set handler failed!\n");
		return ret;
	}

	ret = fiq_glue_register_handler(&fiq_glue_handler);
	if (ret) {
		printk(KERN_ERR"fiqglue: cannot register fig glue handler\n");
		goto error_register;
	}

	return 0;

error_register:
	fiq_glue_handler_reset();
	return ret;
}

static void fiq_enable_helper(void *args)
{
	bool enable = *((bool *)args);

	if (enable) {
		local_fiq_enable();
	} else {
		local_fiq_disable();
	}
}

static int fiq_create_new_node(struct fiq_num_node **node_ptr,
	void (*fiq_callback_func)(unsigned int),
	unsigned int fiq_num)
{
	struct fiq_num_node *fiq_node = NULL;
	struct fiq_callback_node *callback_node = NULL;

	fiq_node = kzalloc(sizeof(*fiq_node), GFP_KERNEL);
	if (!fiq_node) {
		printk(KERN_ERR "[%s]kmalloc for first fiq node failed!\n", __func__);
		return -ENOMEM;
	}

	callback_node = kzalloc(sizeof(*callback_node), GFP_KERNEL);
	if (!callback_node) {
		printk(KERN_ERR "[%s]kmalloc for callback node failed!\n", __func__);
		goto error_kzalloc;
	}

	/* store input parameters */
	fiq_node->fiq_num = fiq_num;
	/* init callback function list */
	INIT_LIST_HEAD(&fiq_node->callback_head);

	/* mount callback function */
	callback_node->callback = fiq_callback_func;
	list_add_tail(&callback_node->node, &fiq_node->callback_head);

	*node_ptr = fiq_node;
	return 0;

error_kzalloc:
	kfree(fiq_node);
	return -ENOMEM;
}

static int fiq_register_for_exist_node(struct fiq_num_node *fiq_node,
	void (*fiq_callback_func)(unsigned int))
{
	struct fiq_callback_node *callback_node;

#ifdef CONFIG_RTOS_HAL_FIQ_GLUE_EARLY
	/* if node registered with early fiq callback, unregister them at first */
	unregister_early_fiq_callback_node(fiq_node);
#endif

	callback_node = fiq_find_callback(fiq_node, fiq_callback_func);
	if (callback_node) {
		printk(KERN_ERR "[%s]FIQ%d callback function has exist.\n",
				__func__, fiq_node->fiq_num);
		return ERR_CALLBACK_EXIST;
	}

	callback_node = kzalloc(sizeof(*callback_node), GFP_KERNEL);
	if (!callback_node) {
		printk(KERN_ERR "[%s]kmalloc for callback node failed!\n", __func__);
		return -ENOMEM;
	}

	/* mount callback function */
	callback_node->callback = fiq_callback_func;
	list_add_tail(&callback_node->node, &fiq_node->callback_head);

	return 0;
}

int fiq_first_register(void (*fiq_callback_func)(unsigned int),
	unsigned int fiq_num)
{
	struct fiq_num_node *fiq_node = NULL;
	bool fiq_enable;
	int ret = 0;

	online_cpus = num_online_cpus();
	fiq_enable = false;
	on_each_cpu(fiq_enable_helper, &fiq_enable, true);

	ret = fiq_create_new_node(&fiq_node, fiq_callback_func, fiq_num);
	if (ret) {
		printk(KERN_ERR "[%s]create for a new node failed!\n", __func__);
		return ret;
	}

	/* init list entry */
	INIT_LIST_HEAD(&fiq_priv.entry);
	list_add_tail(&fiq_node->node, &fiq_priv.entry);

	ret = fiq_glue_init();
	if (ret) {
		printk(KERN_ERR "[%s]fiq glue init error:%d.\n", __func__, ret);
		goto error_kree;
	}

	ret = fiq_priv_init();
	if (ret) {
		printk(KERN_ERR "[%s]fiq priv init error:%d.\n", __func__, ret);
		goto error_init;
	}

	fiq_enable = true;
	on_each_cpu(fiq_enable_helper, &fiq_enable, true);

	return 0;

error_init:
	fiq_glue_handler_reset();
error_kree:
	fiq_free_callback(fiq_node);
	list_del(&fiq_node->node);
	kfree(fiq_node);
	return ret;
}

int fiq_more_register(void (*fiq_callback_func)(unsigned int),
	unsigned int fiq_num)
{
	struct fiq_num_node *fiq_node;
	int ret = 0;

	fiq_node = fiq_find_node(fiq_num);
	if (!fiq_node) {
		/* create new node if no existed node found */
		ret = fiq_create_new_node(&fiq_node, fiq_callback_func, fiq_num);
		if (ret < 0) {
			printk(KERN_ERR "[%s]create for a new node failed!\n",
				__func__);
			return ret;
		}

		list_add_tail(&fiq_node->node, &fiq_priv.entry);
	} else {
		/* mount according to registered callback if exited node found */
		ret = fiq_register_for_exist_node(fiq_node, fiq_callback_func);
	}
	return ret;
}

void fiq_register_handle(int (*init)(void), void (*clean)(void))
{
	fiq_priv.callback.fiq_init_handle = init;
	fiq_priv.callback.fiq_cleansourse_handle = clean;
}

void fiq_register_gic(struct fiq_gic_handle *handle)
{
	if (!handle) {
		printk(KERN_WARNING"[%s]gic handle is NULL.\n", __func__);
		return;
	}
	memcpy(&fiq_priv.gic_handle, handle, sizeof(struct fiq_gic_handle));
}

